package com.inepex.inechart.shared.linechart;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;

import com.inepex.inechart.shared.CartesianAssist;
import com.inepex.inechart.shared.data.KeyValueDataEntry;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class LineChartModelAssist {
	
	protected final CartesianAssist cartesianAssist;
	protected PointFilter pointFilter;

	public LineChartModelAssist(CartesianAssist cartesianAssist) {
		this.cartesianAssist = cartesianAssist;
	}
	
	public Point createDataPoint(KeyValueDataEntry entry, boolean unfilterable){
		Point dp = new Point(entry);
		setDataPoint(dp);
		return dp;
	}
	
	public void setDataPoint(Point dataPoint) {
		double[] data = dataPoint.getUnderLyingData();
		double[] canvasPos = cartesianAssist.modelToCanvas(data[0], data[1]);
		dataPoint.canvasX = canvasPos[0];
		dataPoint.canvasY = canvasPos[1];
		dataPoint.isInViewport = data[0] >= cartesianAssist.getxAxis().getMin() && data[0] <= cartesianAssist.getxAxis().getMax() &&
				 data[1] >= cartesianAssist.getyAxis().getMin() &&  data[1] <= cartesianAssist.getyAxis().getMax();
	}
	
	/**
	 * curve.dataPoints and
	 * curve.discontinuities must be cleared in the given range.
	 * curve.selectedPoints must be sorted
	 * only points in (]/[)from,to(]/[) will be updated
	 * @param curve
	 * @param from
	 * @param to
	 * @param includeFrom
	 * @param includeTo
	 */
	protected void calculatePointsForSortedCurve(Curve curve, double from, double to, boolean includeFrom, boolean includeTo){
		List<KeyValueDataEntry> dataPairs = curve.dataSet.getEntriesByKey(from,  to);
		ArrayList<Point> points = new ArrayList<Point>();
		for(KeyValueDataEntry xyEntry : dataPairs){
			double[] vp = xyEntry.getValuePair();
			if(vp[0] > to || (!includeTo && vp[0] == to)){
				break;
			}
			if(vp[0] > from || (includeFrom && vp[0] == from)){
				Point dp = createDataPoint(xyEntry, false);
				if(curve.unfilterableEntries.contains(xyEntry)){
					dp.unfilterable = true;
				}
				points.add(dp);
				if(curve.discontinuities.contains(xyEntry)){
					curve.discontinuitiesAsPoint.add(dp);
				}
			}
		}
		curve.points.addAll(pointFilter.filterDataPoints(points));
	}

	protected void calculatePointsForCurve(Curve curve){
		List<KeyValueDataEntry> dataPairs = curve.dataSet.getEntries();
		curve.discontinuitiesAsPoint.clear();
		curve.points.clear();
		curve.visiblePoints.clear();
		curve.entryPointMap.clear();
		ArrayList<Point> points = new ArrayList<Point>();
		if(curve.dataSet.isSortable()){
			KeyValueDataEntry last = null;
			KeyValueDataEntry beforeVPMin = null;
			KeyValueDataEntry beforeVPMax = null;
			KeyValueDataEntry afterVPMax = null;
			KeyValueDataEntry afterVPMin = null;
			boolean firstAfterVP = true;
			for(KeyValueDataEntry xyEntry : dataPairs){
				double[] vp = xyEntry.getValuePair();
				//bigger than vp min
				if(vp[0] >= cartesianAssist.getxAxis().getMin()){
					if(last != null && points.size() == 0){
						Point dp = createDataPoint(last, false); 
						if(curve.unfilterableEntries.contains(last)){
							dp.unfilterable = true;
						}
						points.add(dp);
						if(curve.discontinuities.contains(last)){
							curve.discontinuitiesAsPoint.add(dp);
						}
					}
					Point dp = createDataPoint(xyEntry, false); 
					if(curve.unfilterableEntries.contains(xyEntry)){
						dp.unfilterable = true;
					}
					points.add(dp);
					if(curve.discontinuities.contains(xyEntry)){
						curve.discontinuitiesAsPoint.add(dp);
					}
				}
				//after vp
				else if(vp[0] > cartesianAssist.getxAxis().getMax()){
					if(firstAfterVP){
						Point dp = createDataPoint(xyEntry, false); 
						if(curve.unfilterableEntries.contains(xyEntry)){
							dp.unfilterable = true;
						}
						points.add(dp);
						if(curve.discontinuities.contains(xyEntry)){
							curve.discontinuitiesAsPoint.add(dp);
						};
						firstAfterVP = false;
					}
					if(afterVPMin == null){
						afterVPMax = afterVPMin = xyEntry;
					}
					else{
						double[] afterVPMinVP = afterVPMin.getValuePair();
						if(afterVPMinVP[0] > vp[0]){
							afterVPMin = xyEntry;
						}
						if(afterVPMinVP[0] < vp[0]){
							afterVPMax = xyEntry;
						}
					}
				}
				//before vp
				else{
					if(beforeVPMin == null){
						beforeVPMax = beforeVPMin = xyEntry;
					}
					else{
						double[] beforeVPMinVP = beforeVPMin.getValuePair();
						if(beforeVPMinVP[0] > vp[0]){
							beforeVPMin = xyEntry;
						}
						if(beforeVPMinVP[0] < vp[0]){
							beforeVPMax = xyEntry;
						}
					}
				}
				last = xyEntry;
			}
			curve.visiblePoints = points = pointFilter.filterDataPoints(points);
			if(beforeVPMin != null){
				Point dp = createDataPoint(beforeVPMin, false);
				if(points.size() == 0 || Point.canvasXComparator.compare(dp, points.get(0)) != 0){
					curve.points.add(dp);
					curve.entryPointMap.put(beforeVPMin, dp);
				}
			}
			if(beforeVPMax != null && beforeVPMax != beforeVPMin){
				Point dp = createDataPoint(beforeVPMax, false);
				if(points.size() == 0 || Point.canvasXComparator.compare(dp, points.get(0)) != 0){
					curve.points.add(dp);
					curve.entryPointMap.put(beforeVPMax, dp);
				}
			}
			curve.points.addAll(points);
			if(afterVPMin != null){
				Point dp = createDataPoint(afterVPMin, false);
				if(points.size() == 0 || Point.canvasXComparator.compare(dp, points.get(points.size() - 1)) != 0){
					curve.points.add(dp);
					curve.entryPointMap.put(afterVPMin, dp);
				}
			}
			if(afterVPMax != null && afterVPMax != afterVPMin){
				Point dp = createDataPoint(afterVPMax, false);
				if(points.size() == 0 || Point.canvasXComparator.compare(dp, points.get(points.size() - 1)) != 0){
					curve.points.add(dp);
					curve.entryPointMap.put(afterVPMax, dp);
				}
			}
		}
		else{
			for(KeyValueDataEntry entry : dataPairs){
				Point dp = createDataPoint(entry, false); 
				if(curve.unfilterableEntries.contains(entry)){
					dp.unfilterable = true;
				}
				points.add(dp);			
			}
			points = pointFilter.filterDataPoints(points);
			curve.points.addAll(points);
		}
		for(Point dp : points){
			if(dp.containsHiddenData()){
				for(Point dp2 : dp.filteredPoints){
					curve.entryPointMap.put(dp2.getEntry(), dp);
				}
			}
			else{
				curve.entryPointMap.put(dp.getEntry(), dp);
			}
		}
	}
	
	public PointFilter getPointFilter() {
		return pointFilter;
	}

	public void setPointFilter(PointFilter pointFilter) {
		this.pointFilter = pointFilter;
		pointFilter.setLineChartModelAssist(this);
	}
	
	protected ArrayList<Point> getPoints(Curve c){
		return c.points;
	}
	
	protected ArrayList<Point> getDiscontinuitiesAsPoints(Curve c){
		return c.discontinuitiesAsPoint;
	}
	
	protected ArrayList<Point> getVisiblePoints(Curve c){
		return c.visiblePoints;
	}
	
	protected TreeMap<KeyValueDataEntry, Point> getEntryPointMap(Curve c){
		return c.entryPointMap;
	}
	
	protected ArrayList<KeyValueDataEntry> getUnfilterableEntries(Curve c){
		return c.unfilterableEntries;
	}

	protected ArrayList<KeyValueDataEntry> getSelectedEntries(Curve c){
		return c.selectedEntries;
	}
	
	protected ArrayList<KeyValueDataEntry> getDiscontinuityEntries(Curve c){
		return c.selectedEntries;
	}
}
